/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#import "TBinaryProtocol.h"
#import "TObjective-C.h"
#import "TProtocolException.h"

int32_t VERSION_1 = 0x80010000;
int32_t VERSION_MASK = 0xffff0000;

static TBinaryProtocolFactory* gSharedFactory = nil;

@implementation TBinaryProtocolFactory

+ (TBinaryProtocolFactory*)sharedFactory {
  if (gSharedFactory == nil) {
    gSharedFactory = [[TBinaryProtocolFactory alloc] init];
  }

  return gSharedFactory;
}

- (TBinaryProtocol*)newProtocolOnTransport:(id<TTransport>)transport {
  return [[TBinaryProtocol alloc] initWithTransport:transport];
}

@end

@implementation TBinaryProtocol

- (id)initWithTransport:(id<TTransport>)transport {
  return [self initWithTransport:transport strictRead:NO strictWrite:NO];
}

- (id)initWithTransport:(id<TTransport>)transport
             strictRead:(BOOL)strictRead
            strictWrite:(BOOL)strictWrite {
  self = [super init];
  mTransport = [transport retain_stub];
  mStrictRead = strictRead;
  mStrictWrite = strictWrite;
  return self;
}

- (int32_t)messageSizeLimit {
  return mMessageSizeLimit;
}

- (void)setMessageSizeLimit:(int32_t)sizeLimit {
  mMessageSizeLimit = sizeLimit;
}

- (void)dealloc {
  [mTransport release_stub];
  [super dealloc_stub];
}

- (id<TTransport>)transport {
  return mTransport;
}

- (NSString*)readStringBody:(int)size {
  char* buffer = malloc(size + 1);
  if (!buffer) {
    @throw [TProtocolException
        exceptionWithName:@"TProtocolException"
                   reason:[NSString
                              stringWithFormat:
                                  @"Unable to allocate memory in %s, size: %i",
                                  __PRETTY_FUNCTION__,
                                  size]];
    ;
  }
  [mTransport readAll:(uint8_t*)buffer offset:0 length:size];
  buffer[size] = 0;
  NSString* result = [NSString stringWithUTF8String:buffer];
  free(buffer);
  return result;
}

- (void)readMessageBeginReturningName:(NSString**)name
                                 type:(int*)type
                           sequenceID:(int*)sequenceID {
  int32_t size = [self readI32];
  if (size < 0) {
    int version = size & VERSION_MASK;
    if (version != VERSION_1) {
      @throw [TProtocolException
          exceptionWithName:@"TProtocolException"
                     reason:@"Bad version in readMessageBegin"];
    }
    if (type != NULL) {
      *type = size & 0x00FF;
    }
    NSString* messageName = [self readString];
    if (name != NULL) {
      *name = messageName;
    }
    int seqID = [self readI32];
    if (sequenceID != NULL) {
      *sequenceID = seqID;
    }
  } else {
    if (mStrictRead) {
      @throw [TProtocolException
          exceptionWithName:@"TProtocolException"
                     reason:
                         @"Missing version in readMessageBegin, old client?"];
    }
    if ([self messageSizeLimit] > 0 && size > [self messageSizeLimit]) {
      @throw [TProtocolException
          exceptionWithName:@"TProtocolException"
                     reason:
                         [NSString
                             stringWithFormat:
                                 @"Message too big.  Size limit is: %d Message size is: %d",
                                 mMessageSizeLimit,
                                 size]];
    }
    NSString* messageName = [self readStringBody:size];
    if (name != NULL) {
      *name = messageName;
    }
    int messageType = [self readByte];
    if (type != NULL) {
      *type = messageType;
    }
    int seqID = [self readI32];
    if (sequenceID != NULL) {
      *sequenceID = seqID;
    }
  }
}

- (void)readMessageEnd {
}

- (void)readStructBeginReturningName:(NSString**)name {
  if (name != NULL) {
    *name = nil;
  }
}

- (void)readStructEnd {
}

- (void)readFieldBeginReturningName:(NSString**)name
                               type:(int*)fieldType
                            fieldID:(int*)fieldID {
  if (name != NULL) {
    *name = nil;
  }
  int ft = [self readByte];
  if (fieldType != NULL) {
    *fieldType = ft;
  }
  if (ft != TType_STOP) {
    int fid = [self readI16];
    if (fieldID != NULL) {
      *fieldID = fid;
    }
  }
}

- (void)readFieldEnd {
}

- (int32_t)readI32 {
  uint8_t i32rd[4];
  [mTransport readAll:i32rd offset:0 length:4];
  return ((i32rd[0] & 0xff) << 24) | ((i32rd[1] & 0xff) << 16) |
      ((i32rd[2] & 0xff) << 8) | ((i32rd[3] & 0xff));
}

- (NSString*)readString {
  int size = [self readI32];
  return [self readStringBody:size];
}

- (BOOL)readBool {
  return [self readByte] == 1;
}

- (uint8_t)readByte {
  uint8_t myByte;
  [mTransport readAll:&myByte offset:0 length:1];
  return myByte;
}

- (short)readI16 {
  uint8_t buff[2];
  [mTransport readAll:buff offset:0 length:2];
  return (short)(((buff[0] & 0xff) << 8) | ((buff[1] & 0xff)));
  return 0;
}

- (int64_t)readI64;
{
  uint8_t i64rd[8];
  [mTransport readAll:i64rd offset:0 length:8];
  return ((int64_t)(i64rd[0] & 0xff) << 56) |
      ((int64_t)(i64rd[1] & 0xff) << 48) | ((int64_t)(i64rd[2] & 0xff) << 40) |
      ((int64_t)(i64rd[3] & 0xff) << 32) | ((int64_t)(i64rd[4] & 0xff) << 24) |
      ((int64_t)(i64rd[5] & 0xff) << 16) | ((int64_t)(i64rd[6] & 0xff) << 8) |
      ((int64_t)(i64rd[7] & 0xff));
}

- (double)readDouble;
{
  // FIXME - will this get us into trouble on PowerPC?
  int64_t ieee754 = [self readI64];
  return *((double*)&ieee754);
}

- (NSData*)readBinary {
  int32_t size = [self readI32];
  uint8_t* buff = malloc(size);
  if (buff == NULL) {
    @throw [TProtocolException
        exceptionWithName:@"TProtocolException"
                   reason:
                       [NSString
                           stringWithFormat:
                               @"Out of memory.  Unable to allocate %d bytes trying to read binary data.",
                               size]];
  }
  [mTransport readAll:buff offset:0 length:size];
  return [NSData dataWithBytesNoCopy:buff length:size];
}

- (void)readMapBeginReturningKeyType:(int*)keyType
                           valueType:(int*)valueType
                                size:(int*)size {
  int kt = [self readByte];
  int vt = [self readByte];
  int s = [self readI32];
  if (keyType != NULL) {
    *keyType = kt;
  }
  if (valueType != NULL) {
    *valueType = vt;
  }
  if (size != NULL) {
    *size = s;
  }
}

- (void)readMapEnd {
}

- (void)readSetBeginReturningElementType:(int*)elementType size:(int*)size {
  int et = [self readByte];
  int s = [self readI32];
  if (elementType != NULL) {
    *elementType = et;
  }
  if (size != NULL) {
    *size = s;
  }
}

- (void)readSetEnd {
}

- (void)readListBeginReturningElementType:(int*)elementType size:(int*)size {
  int et = [self readByte];
  int s = [self readI32];
  if (elementType != NULL) {
    *elementType = et;
  }
  if (size != NULL) {
    *size = s;
  }
}

- (void)readListEnd {
}

- (void)writeByte:(uint8_t)value {
  [mTransport write:&value offset:0 length:1];
}

- (void)writeMessageBeginWithName:(NSString*)name
                             type:(int)messageType
                       sequenceID:(int)sequenceID {
  if (mStrictWrite) {
    int version = VERSION_1 | messageType;
    [self writeI32:version];
    [self writeString:name];
    [self writeI32:sequenceID];
  } else {
    [self writeString:name];
    [self writeByte:messageType];
    [self writeI32:sequenceID];
  }
}

- (void)writeMessageEnd {
}

- (void)writeStructBeginWithName:(NSString*)name {
}

- (void)writeStructEnd {
}

- (void)writeFieldBeginWithName:(NSString*)name
                           type:(int)fieldType
                        fieldID:(int)fieldID {
  [self writeByte:fieldType];
  [self writeI16:fieldID];
}

- (void)writeI32:(int32_t)value {
  uint8_t buff[4];
  buff[0] = 0xFF & (value >> 24);
  buff[1] = 0xFF & (value >> 16);
  buff[2] = 0xFF & (value >> 8);
  buff[3] = 0xFF & value;
  [mTransport write:buff offset:0 length:4];
}

- (void)writeI16:(short)value {
  uint8_t buff[2];
  buff[0] = 0xff & (value >> 8);
  buff[1] = 0xff & value;
  [mTransport write:buff offset:0 length:2];
}

- (void)writeI64:(int64_t)value {
  uint8_t buff[8];
  buff[0] = 0xFF & (value >> 56);
  buff[1] = 0xFF & (value >> 48);
  buff[2] = 0xFF & (value >> 40);
  buff[3] = 0xFF & (value >> 32);
  buff[4] = 0xFF & (value >> 24);
  buff[5] = 0xFF & (value >> 16);
  buff[6] = 0xFF & (value >> 8);
  buff[7] = 0xFF & value;
  [mTransport write:buff offset:0 length:8];
}

- (void)writeDouble:(double)value {
  // spit out IEEE 754 bits - FIXME - will this get us in trouble on
  // PowerPC?
  [self writeI64:*((int64_t*)&value)];
}

- (void)writeString:(NSString*)value {
  if (value != nil) {
    const char* utf8Bytes = [value UTF8String];
    size_t length = strlen(utf8Bytes);
    [self writeI32:(int32_t)length];
    [mTransport write:(uint8_t*)utf8Bytes offset:0 length:(unsigned int)length];
  } else {
    // instead of crashing when we get null, let's write out a zero
    // length string
    [self writeI32:0];
  }
}

- (void)writeBinary:(NSData*)data {
  [self writeI32:(int32_t)[data length]];
  [mTransport write:[data bytes] offset:0 length:(int32_t)[data length]];
}

- (void)writeFieldStop {
  [self writeByte:TType_STOP];
}

- (void)writeFieldEnd {
}

- (void)writeMapBeginWithKeyType:(int)keyType
                       valueType:(int)valueType
                            size:(int)size {
  [self writeByte:keyType];
  [self writeByte:valueType];
  [self writeI32:size];
}

- (void)writeMapEnd {
}

- (void)writeSetBeginWithElementType:(int)elementType size:(int)size {
  [self writeByte:elementType];
  [self writeI32:size];
}

- (void)writeSetEnd {
}

- (void)writeListBeginWithElementType:(int)elementType size:(int)size {
  [self writeByte:elementType];
  [self writeI32:size];
}

- (void)writeListEnd {
}

- (void)writeBool:(BOOL)value {
  [self writeByte:(value ? 1 : 0)];
}

@end
